/*
 * Copyright 2013 Google Inc.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 * 02110-1301, USA.
 */
/*
 * Author: ncardwell@google.com (Neal Cardwell)
 *
 * Implementation for a representation of TCP/IP packets.
 * Packets are represented in their wire format.
 */

#include "packet.h"

#include <stdlib.h>
#include <string.h>
#include "assert.h"
#include "ethernet.h"
#include "gre_packet.h"
#include "ip_packet.h"
#include "logging.h"
#include "mpls_packet.h"


/* Info for all types of header we support. */
struct header_type_info header_types[HEADER_NUM_TYPES] = {
	{ "NONE",   0,			0,		NULL },
	{ "IPV4",   IPPROTO_IPIP,	ETHERTYPE_IP,	ipv4_header_finish },
	{ "IPV6",   IPPROTO_IPV6,	ETHERTYPE_IPV6, ipv6_header_finish },
	{ "GRE",    IPPROTO_GRE,	0,		gre_header_finish },
	{ "MPLS",   0,			ETHERTYPE_MPLS_UC, mpls_header_finish },
	{ "TCP",    IPPROTO_TCP,	0,		NULL },
	{ "UDP",    IPPROTO_UDP,	0,		NULL },
	{ "ICMPV4", IPPROTO_ICMP,	0,		NULL },
	{ "ICMPV6", IPPROTO_ICMPV6,	0,		NULL },
};

struct packet *packet_new(u32 buffer_bytes)
{
	struct packet *packet = calloc(1, sizeof(struct packet));
	packet->buffer = malloc(buffer_bytes);
	packet->buffer_bytes = buffer_bytes;
	return packet;
}

void packet_free(struct packet *packet)
{
	free(packet->buffer);
	memset(packet, 0, sizeof(*packet));  /* paranoia to help catch bugs */
	free(packet);
}

struct packet_list *packet_list_new(void)
{
	struct packet_list *list = calloc(1, sizeof(struct packet_list));
	list->packet = NULL;
	list->next = NULL;
	return list;
}

void packet_list_free(struct packet_list *list)
{
	while (list != NULL) {
		struct packet_list *dead_list = list;
		if (list->packet)
			packet_free(list->packet);
		list = list->next;
		free(dead_list);
	}
}

int packet_header_count(const struct packet *packet)
{
	int i;

	for (i = 0; i < ARRAY_SIZE(packet->headers); ++i) {
		if (packet->headers[i].type == HEADER_NONE)
			break;
	}
	return i;
}

/* Copy any header info from old_packet to new_packet. */
static void packet_copy_headers(struct packet *new_packet,
				struct packet *old_packet,
				int bytes_headroom)
{
	int i;
	u8 *base = new_packet->buffer + bytes_headroom;

	for (i = 0; i < ARRAY_SIZE(old_packet->headers); ++i) {
		struct header *old_header = &old_packet->headers[i];
		struct header *new_header = &new_packet->headers[i];
		int offset = 0;

		if (old_header->type == HEADER_NONE)
			break;
		offset = old_header->h.ptr - old_packet->buffer;
		new_header->h.ptr		= base + offset;
		new_header->header_bytes	= old_header->header_bytes;
		new_header->total_bytes		= old_header->total_bytes;
		new_header->type		= old_header->type;
	}
}

struct header *packet_append_header(struct packet *packet,
				    enum header_t header_type,
				    int header_bytes)
{
	struct header *header = NULL;
	int num_headers = packet_header_count(packet);
	int packet_bytes;

	assert(num_headers <= PACKET_MAX_HEADERS);
	if (num_headers == PACKET_MAX_HEADERS)
		return NULL;

	header = &packet->headers[num_headers];

	if (packet->ip_bytes + header_bytes > packet->buffer_bytes)
		return NULL;
	packet_bytes = packet->l2_header_bytes + packet->ip_bytes;
	header->h.ptr = packet->buffer + packet_bytes;
	packet->ip_bytes += header_bytes;

	header->type = header_type;
	header->header_bytes = header_bytes;
	header->total_bytes = 0;
	return header;
}

/* Map a pointer to a packet offset from an old base to a new base. */
static void *offset_ptr(u8 *old_base, u8* new_base, void *old_ptr)
{
	u8 *old = (u8*)old_ptr;

	return (old == NULL) ? NULL : (new_base + (old - old_base));
}

static void packet_duplicate_info(struct packet *packet,
				  struct packet *old_packet,
				  int bytes_headroom,
				  int extra_payload)
{
	u8 *old_base = old_packet->buffer;
	u8 *new_base = packet->buffer + bytes_headroom;

	packet->ip_bytes	= old_packet->ip_bytes + extra_payload;
	packet->direction	= old_packet->direction;
	packet->time_usecs	= old_packet->time_usecs;
	packet->flags		= old_packet->flags;
	packet->tos_chk		= old_packet->tos_chk;

	packet_copy_headers(packet, old_packet, bytes_headroom);

	/* Set up layer 3 header pointer. */
	packet->ipv4	= offset_ptr(old_base, new_base, old_packet->ipv4);
	packet->ipv6	= offset_ptr(old_base, new_base, old_packet->ipv6);
	packet->tcp	= offset_ptr(old_base, new_base, old_packet->tcp);
	packet->udp	= offset_ptr(old_base, new_base, old_packet->udp);
	packet->icmpv4	= offset_ptr(old_base, new_base, old_packet->icmpv4);
	packet->icmpv6	= offset_ptr(old_base, new_base, old_packet->icmpv6);

	packet->tcp_ts_val	= offset_ptr(old_base, new_base,
					     old_packet->tcp_ts_val);
	packet->tcp_ts_ecr	= offset_ptr(old_base, new_base,
					     old_packet->tcp_ts_ecr);
	packet->echoed_header		= old_packet->echoed_header;
}

/* Make a copy of the given old packet, but in the new copy reserve the
 * given number of bytes of headroom at the start of the packet->buffer.
 * This empty headroom can later be filled with outer packet headers.
 * A slow but simple model.
 */
static struct packet *packet_copy_with_headroom(struct packet *old_packet,
						int bytes_headroom)
{
	/* Allocate a new packet and copy link layer header and IP datagram. */
	const int bytes_used = packet_end(old_packet) - old_packet->buffer;
	assert(bytes_used >= 0);
	assert(bytes_used <= 128*1024);
	struct packet *packet = packet_new(bytes_headroom + bytes_used);
	u8 *old_base = old_packet->buffer;
	u8 *new_base = packet->buffer + bytes_headroom;

	memcpy(new_base, old_base, bytes_used);

	packet_duplicate_info(packet, old_packet, bytes_headroom, 0);

	return packet;
}

struct packet *packet_copy(struct packet *old_packet)
{
	return packet_copy_with_headroom(old_packet, 0);
}

/* Finalize all the headers once we know what's inside inner layers. */
static void packet_finish_encapsulation_headers(struct packet *packet)
{
	int i;
	struct header *header = NULL, *next = NULL;

	/* Proceed from inner to outer. */
	for (i = ARRAY_SIZE(packet->headers) - 1; i >= 0; --i, next = header) {
		struct header_type_info *type_info = NULL;

		header = &packet->headers[i];
		if (header->type == HEADER_NONE)
			continue;

		type_info = header_type_info(header->type);
		if (type_info->finish != NULL)
			type_info->finish(packet, header, next);
	}
}

struct packet *packet_encapsulate(struct packet *outer, struct packet *inner)
{
	struct packet *packet = NULL;
	const int outer_headers = packet_header_count(outer);
	const int inner_headers = packet_header_count(inner);

	assert(outer_headers + inner_headers <= PACKET_MAX_HEADERS);

	/* Copy the inner packet bits and header metadata. */
	packet = packet_copy_with_headroom(inner, outer->ip_bytes);

	/* Copy over the bits in the outer headers. */
	memcpy(packet->buffer, outer->buffer, outer->ip_bytes);

	/* Move the inner header metadata to make room for the outer. */
	memmove(packet->headers + outer_headers, packet->headers + 0,
		inner_headers * sizeof(struct header));

	/* Copy over the metadata about the outer headers. */
	packet_copy_headers(packet, outer, 0);

	assert(packet_header_count(packet) == outer_headers + inner_headers);

	packet_finish_encapsulation_headers(packet);

	packet->ip_bytes = outer->ip_bytes + inner->ip_bytes;

	return packet;
}

struct header_type_info *header_type_info(enum header_t header_type)
{
	assert(header_type > HEADER_NONE);
	assert(header_type < HEADER_NUM_TYPES);
	assert(ARRAY_SIZE(header_types) == HEADER_NUM_TYPES);
	return &header_types[header_type];
}

/* Aggregate a list of input packets into a single output packet. */
struct packet *aggregate_packets(const struct packet_list *head,
				 const struct packet_list *tail,
				 int payload_size)
{
	int i;
	/* Copy the headers from the last source packet. */
	struct packet *first_packet = head->packet;
	struct packet *last_packet = tail->packet;
	struct packet *old_packet = last_packet;
	/* Allocate a new packet that can accommodate the combined payload */
	int extra_payload = payload_size - packet_payload_len(old_packet);
	int headers_len = packet_payload(old_packet) - old_packet->buffer;
	int old_packet_size = packet_end(old_packet) - old_packet->buffer;
	struct packet *packet = packet_new(old_packet_size + extra_payload);

	u8 *old_base = old_packet->buffer;
	u8 *new_base = packet->buffer;
	u8 *iter_base = new_base + headers_len;

	DEBUGP("aggregate_packets with combined payload size of %d bytes\n",
	       payload_size);
	memcpy(new_base, old_base, headers_len);

	/* Copy the payload from all the source packets. */
	do {
		memcpy(iter_base, packet_payload(head->packet),
		       packet_payload_len(head->packet));
		iter_base += packet_payload_len(head->packet);
		head = head->next;
	} while (head != NULL);

	packet_duplicate_info(packet, old_packet, 0, extra_payload);

	/* Adjust header bytes information to account for the larger payload. */
	for (i = 0; i < ARRAY_SIZE(packet->headers); ++i) {
		struct header *new_header = &packet->headers[i];

		if (new_header->type == HEADER_NONE)
			break;
		new_header->total_bytes += extra_payload;
		DEBUGP("%s header starts at %p\n",
			header_type_info(new_header->type)->name,
			new_header->h.ptr);
		/* For TCP header, we must copy the seq number and the cwr flag
		 * from the first packet.
		 */
		if (new_header->type == HEADER_TCP) {
			assert(packet->tcp != NULL);
			assert(first_packet->tcp != NULL);
			packet->tcp->seq = first_packet->tcp->seq;
			packet->tcp->cwr = first_packet->tcp->cwr;
		}
	}
	packet_finish_encapsulation_headers(packet);

	return packet;
}
